#include "ai_talk_ota.h"
#include "utils_log.h"
#include <nlohmann/json.hpp>
#include <curl/curl.h>
#include <sstream>
#include <string>
#include <vector>
#include <iomanip>        // 用于 std::setw, std::setfill
#include <openssl/hmac.h> // 如果使用OpenSSL，请包含对应的HMAC头文件
#include <openssl/sha.h>  // 用于 SHA256_DIGEST_LENGTH
// #include <openssl/hmac.h> // Include appropriate HMAC header if using OpenSSL
#include <thread> // 用于 std::this_thread::sleep_for
#include <chrono> // 用于 std::chrono::milliseconds

namespace El {
namespace AITalk {

// Constants for activation polling
const int kMaxActivationPollAttempts =
    60; // Max number of polling attempts for activation (aligns with python: 60 * 5s = 5 min)
const int kActivationPollDelayMs = 5000; // Delay in milliseconds between polls after receiving 202 (5 seconds)

AITalkOta &AITalkOta::GetInstance()
{
    static AITalkOta instance; // 使用静态局部变量实现线程安全的单例
    return instance;
}

AITalkOta::AITalkOta() // 私有构造函数
    : serverPort_(0), has_serial_number_(false), has_activation_challenge_(false), activation_timeout_ms_(0)
{
    // Initialization if needed
}

size_t AITalkOta::WriteCallback(void *contents, size_t size, size_t nmemb, void *userp)
{
    // 将libcurl接收到的数据追加到字符串中
    ((std::string *)userp)->append((char *)contents, size * nmemb);
    return size * nmemb;
}

std::string AITalkOta::GetActivationPayload()
{
    if (!has_serial_number_ || activation_challenge_.empty()) {
        EL_ERROR("无法生成激活负载：缺少序列号或挑战码。");
        return "{}"; // 返回空JSON对象表示失败
    }

    nlohmann::json inner_payload_json;
    inner_payload_json["algorithm"] = "hmac-sha256";
    inner_payload_json["serial_number"] = serial_number_;
    inner_payload_json["challenge"] = activation_challenge_;

    // --- 开始 HMAC 计算 ---
    // TODO: HMAC 密钥 (hmac_key_str) 需要被安全地设置到 AITalkOta 实例中。
    // 这个密钥通常在设备首次配置时生成或烧录，并从安全存储（如efuse、安全元件或加密文件）中读取。
    // 以下是一个占位符，实际项目中需要替换为真实的密钥获取逻辑。
    std::string hmac_key_str =
        "b5bf012dd518080532f928b70ed958799f34f9224e80dd4128795a70a5baca24"; // <-- 重要: 替换为实际的密钥获取逻辑
    if (hmac_key_str.empty()) {
        EL_WARN("HMAC密钥为空或未设置，将发送空的HMAC签名。请确保设备已正确配置HMAC密钥。");
        inner_payload_json["hmac"] = "";
    } else {
        unsigned char hmac_digest[SHA256_DIGEST_LENGTH];
        unsigned int hmac_digest_len = 0; // OpenSSL HMAC函数会填充这个值

        HMAC(EVP_sha256(),                                         // 哈希算法
             hmac_key_str.c_str(),                                 // 密钥
             static_cast<int>(hmac_key_str.length()),              // 密钥长度
             (const unsigned char *)activation_challenge_.c_str(), // 消息 (挑战码)
             static_cast<int>(activation_challenge_.length()),     // 消息长度
             hmac_digest,                                          // 输出的摘要
             &hmac_digest_len);                                    // 输出的摘要长度

        if (hmac_digest_len > 0) {
            std::stringstream ss;
            ss << std::hex << std::setfill('0');
            for (unsigned int i = 0; i < hmac_digest_len; ++i) {
                ss << std::setw(2) << static_cast<unsigned int>(hmac_digest[i]);
            }
            inner_payload_json["hmac"] = ss.str();
            EL_INFO("成功生成HMAC签名。");
        } else {
            EL_ERROR("HMAC签名计算失败 (摘要长度为0)。将发送空的HMAC。");
            inner_payload_json["hmac"] = "";
        }
    }
    // --- 结束 HMAC 计算 ---

    // Wrap in outer "Payload" object as per device_activator.py
    nlohmann::json outer_payload_json;
    outer_payload_json["Payload"] = inner_payload_json;

    std::string json_str = outer_payload_json.dump();
    EL_INFO("生成激活负载: {}", json_str.c_str());
    return json_str;
}

bool AITalkOta::Start(const std::string &ota_url, const std::string &device_id, const std::string &client_id)
{
    device_id_ = device_id;
    client_id_ = client_id;

    EL_INFO("启动OTA与激活流程...");
    bool fetch_success = CheckVersion(ota_url, device_id, client_id);

    if (!fetch_success) {
        EL_ERROR("CheckVersion过程失败，无法继续。");
        return false;
    }

    if (HasActivationChallenge()) {
        EL_INFO("检测到需要设备激活。激活提示消息: '{}'", activation_message_.c_str());
        if (!activation_code_.empty()) {
            EL_INFO("设备激活验证码: {} (请在指定平台输入此验证码)", activation_code_.c_str());
        }
        EL_INFO("尝试激活...");
        ActivationStatus status = Activate();

        if (status == ActivationStatus::SUCCESS) {
            EL_INFO("设备激活成功。");
            return true;
        } else { // FAILURE or ERROR from Activate
            EL_ERROR("设备激活失败或出错 (status: {})。", static_cast<int>(status));
            return false;
        }
    } else {
        EL_INFO("无需设备激活，或已激活。");
        return true;
    }
}

bool AITalkOta::CheckVersion(const std::string &ota_url, const std::string &device_id, const std::string &client_id)
{
    EL_INFO("请求OTA地址: {}", ota_url.c_str());
    ota_url_ = ota_url;                           // 保存基础OTA URL，用于后续激活请求
    serial_number_ = device_id;                   // 假设传入的device_id是序列号
    has_serial_number_ = !serial_number_.empty(); // 设置是否有序列号的标志

    CURL *curl;
    CURLcode res;
    std::string readBuffer; // 用于存储HTTP响应体
    long http_code = 0;     // 用于存储HTTP响应状态码

    curl_global_init(CURL_GLOBAL_DEFAULT);
    curl = curl_easy_init();
    if (curl) {
        nlohmann::json req_json;
        req_json["version"] = 2;
        req_json["language"] = "zh-CN";
        req_json["mac_address"] = device_id;
        req_json["uuid"] = client_id;
        req_json["application"] = {{"name", "ai_talk_app"}, {"version", "1.0.0"}};

        std::string json_payload = req_json.dump();
        EL_INFO("OTA请求负载: {}", json_payload.c_str());

        struct curl_slist *headers = NULL;
        headers = curl_slist_append(headers, "Content-Type: application/json");
        headers = curl_slist_append(headers, "Accept: application/json");
        std::string activation_version_header = "Activation-Version: " + std::string(has_serial_number_ ? "2" : "1");
        headers = curl_slist_append(headers, activation_version_header.c_str());

        std::string device_id_header = "Device-Id: " + device_id;
        headers = curl_slist_append(headers, device_id_header.c_str());

        std::string client_id_header = "Client-Id: " + client_id;
        headers = curl_slist_append(headers, client_id_header.c_str());

        std::string user_agent_header = "User-Agent: BOARD_NAME/1.0.0";
        headers = curl_slist_append(headers, user_agent_header.c_str());
        headers = curl_slist_append(headers, "Accept-Language: zh-CN");

        curl_easy_setopt(curl, CURLOPT_URL, ota_url.c_str());
        curl_easy_setopt(curl, CURLOPT_POSTFIELDS, json_payload.c_str());
        curl_easy_setopt(curl, CURLOPT_POSTFIELDSIZE, (long)json_payload.length());
        curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);
        curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, WriteCallback);
        curl_easy_setopt(curl, CURLOPT_WRITEDATA, &readBuffer);
        curl_easy_setopt(curl, CURLOPT_TIMEOUT_MS, 10000L);
        curl_easy_setopt(curl, CURLOPT_VERBOSE, 0L); // Disabled verbose for cleaner logs, enable with 1L if needed
        curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0L);
        curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 0L);

        res = curl_easy_perform(curl);
        curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &http_code);

        curl_slist_free_all(headers);
        curl_easy_cleanup(curl);

        if (res != CURLE_OK) {
            EL_ERROR("OTA请求 (curl_easy_perform) 失败: {}", curl_easy_strerror(res));
            curl_global_cleanup();
            return false;
        }
    } else {
        EL_ERROR("curl_easy_init() 创建OTA请求失败。");
        curl_global_cleanup();
        return false;
    }

    curl_global_cleanup();

    if (http_code != 200) {
        EL_ERROR("OTA响应码非200: {}. Body: {}", http_code, readBuffer.c_str());
        return false;
    }

    EL_INFO("OTA响应内容: {}", readBuffer.c_str());

    has_activation_challenge_ = false;
    activation_message_.clear();
    activation_code_.clear();
    activation_challenge_.clear();
    activation_timeout_ms_ = 0;

    try {
        nlohmann::json resp_json = nlohmann::json::parse(readBuffer);

        if (resp_json.contains("activation") && resp_json["activation"].is_object()) {
            const auto &activation = resp_json["activation"];
            if (activation.contains("message") && activation["message"].is_string()) {
                activation_message_ = activation["message"].get<std::string>();
                EL_INFO("收到激活消息: {}", activation_message_.c_str());
            }
            if (activation.contains("code") && activation["code"].is_string()) {
                activation_code_ = activation["code"].get<std::string>();
                // Log the activation code for the user/calling application
                EL_INFO("收到激活验证码: {}", activation_code_.c_str());
            }
            if (activation.contains("challenge") && activation["challenge"].is_string()) {
                activation_challenge_ = activation["challenge"].get<std::string>();
                has_activation_challenge_ = !activation_challenge_.empty();
                EL_INFO("收到激活挑战码: {}", has_activation_challenge_ ? activation_challenge_.c_str() : "无");
            }
            if (activation.contains("timeout_ms") && activation["timeout_ms"].is_number_integer()) {
                activation_timeout_ms_ = activation["timeout_ms"].get<int>();
                EL_INFO("收到激活超时: {}ms", activation_timeout_ms_);
            }
        } else {
            EL_INFO("OTA响应中不包含 'activation' 对象。");
        }

        if (resp_json.contains("websocket")) {
            const auto &ws_config = resp_json["websocket"];
            if (ws_config.contains("url") && ws_config["url"].is_string()) {
                std::string ws_url_full = ws_config["url"].get<std::string>();
                std::string ws_url_no_proto = ws_url_full;

                bool is_secure = false;
                if (ws_url_no_proto.rfind("wss://", 0) == 0) {
                    is_secure = true;
                    ws_url_no_proto = ws_url_no_proto.substr(6);
                } else if (ws_url_no_proto.rfind("ws://", 0) == 0) {
                    is_secure = false;
                    ws_url_no_proto = ws_url_no_proto.substr(5);
                } else {
                    EL_WARN("WebSocket URL格式无法识别: {}", ws_url_full.c_str());
                }

                size_t path_pos = ws_url_no_proto.find('/');
                std::string host_part;
                if (path_pos != std::string::npos) {
                    host_part = ws_url_no_proto.substr(0, path_pos);
                    serverPath_ = ws_url_no_proto.substr(path_pos);
                } else {
                    host_part = ws_url_no_proto;
                    serverPath_ = "/";
                }

                size_t port_pos = host_part.find(':');
                std::string address_part;
                int port_part = is_secure ? 443 : 80;
                if (port_pos != std::string::npos) {
                    std::string port_str = host_part.substr(port_pos + 1);
                    address_part = host_part.substr(0, port_pos);
                    try {
                        port_part = std::stoi(port_str);
                    } catch (const std::exception &e) {
                        EL_ERROR("从OTA URL解析端口失败: {}. 使用默认端口.", e.what());
                    }
                } else {
                    address_part = host_part;
                }
                serverAddress_ = (is_secure ? "wss://" : "ws://") + address_part;
                serverPort_ = port_part;

            } else {
                EL_WARN("OTA响应缺少 'websocket.url' 字段。");
            }

            if (ws_config.contains("token") && ws_config["token"].is_string()) {
                accessToken_ = ws_config["token"].get<std::string>();
            } else {
                EL_WARN("OTA响应缺少 'websocket.token' 字段。");
            }

            EL_INFO("OTA成功获取Websocket配置: address={}, port={}, path={}, token是否存在={}", serverAddress_.c_str(),
                     serverPort_, serverPath_.c_str(), !accessToken_.empty());
            return true;
        } else {
            EL_WARN("OTA配置中无 'websocket' 字段. Body: {}", readBuffer.c_str());
            return has_activation_challenge_; // If activation is pending, still consider this part "successful" for now
        }
    } catch (const nlohmann::json::parse_error &e) {
        EL_ERROR("解析OTA响应失败: {}. Body: {}", e.what(), readBuffer.c_str());
        return false;
    } catch (const nlohmann::json::exception &e) {
        EL_ERROR("处理OTA响应JSON时出错: {}. Body: {}", e.what(), readBuffer.c_str());
        return false;
    } catch (const std::exception &e) {
        EL_ERROR("处理OTA响应时发生未知错误: {}. Body: {}", e.what(), readBuffer.c_str());
        return false;
    }
}

ActivationStatus AITalkOta::Activate()
{
    if (!has_activation_challenge_) {
        EL_WARN("没有可用的激活挑战码来执行激活操作。");
        return ActivationStatus::NO_CHALLENGE;
    }
    if (ota_url_.empty()) {
        EL_ERROR("OTA URL未设置，无法执行激活。");
        return ActivationStatus::ERROR;
    }

    std::string activation_url = ota_url_;
    if (activation_url.back() == '/') {
        activation_url += "activate";
    } else {
        activation_url += "/activate";
    }

    EL_INFO("尝试设备激活URL: {}", activation_url.c_str());

    std::string payload = GetActivationPayload();
    if (payload == "{}") {
        EL_ERROR("生成激活负载失败。");
        return ActivationStatus::ERROR;
    }

    CURL *curl = nullptr;
    CURLcode res;
    std::string readBuffer;
    long http_code = 0;
    int polling_attempts = 0;

    curl_global_init(CURL_GLOBAL_DEFAULT); // Ensure global init

    while (polling_attempts < kMaxActivationPollAttempts) {
        readBuffer.clear();
        http_code = 0;
        curl = curl_easy_init();
        if (!curl) {
            EL_ERROR("curl_easy_init() 创建激活请求失败。");
            curl_global_cleanup();
            return ActivationStatus::ERROR;
        }

        struct curl_slist *headers = NULL;
        headers = curl_slist_append(headers, "Content-Type: application/json");
        headers = curl_slist_append(headers, "Accept: application/json");
        std::string activation_version_header = "Activation-Version: " + std::string(has_serial_number_ ? "2" : "1");
        headers = curl_slist_append(headers, activation_version_header.c_str());
        std::string device_id_header = "Device-Id: " + device_id_;
        headers = curl_slist_append(headers, device_id_header.c_str());
        std::string client_id_header = "Client-Id: " + client_id_;
        headers = curl_slist_append(headers, client_id_header.c_str());
        std::string user_agent_header = "User-Agent: BOARD_NAME/1.0.0";
        headers = curl_slist_append(headers, user_agent_header.c_str());
        headers = curl_slist_append(headers, "Accept-Language: zh-CN");

        curl_easy_setopt(curl, CURLOPT_URL, activation_url.c_str());
        curl_easy_setopt(curl, CURLOPT_POSTFIELDS, payload.c_str());
        curl_easy_setopt(curl, CURLOPT_POSTFIELDSIZE, (long)payload.length());
        curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);
        curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, WriteCallback);
        curl_easy_setopt(curl, CURLOPT_WRITEDATA, &readBuffer);
        // Timeout for each polling request. Uses server-provided timeout or a default.
        // The python script has a 10s timeout for the POST, then 5s interval for retries.
        // Here, activation_timeout_ms_ could be for the long poll duration if server supports it,
        // or a shorter timeout for each attempt.
        // For now, we use a shorter timeout for each attempt, and the loop handles retries.
        long request_timeout_ms = (long)activation_timeout_ms_ > 0 ? (long)activation_timeout_ms_ : 15000L;
        if (polling_attempts > 0) { // For subsequent polls, use a potentially shorter specific poll timeout if defined
            // request_timeout_ms = kActivationPollHttpRequestTimeoutMs; // Example: define a specific shorter timeout
            // for polls
        }
        curl_easy_setopt(curl, CURLOPT_TIMEOUT_MS, request_timeout_ms);
        curl_easy_setopt(curl, CURLOPT_VERBOSE, 0L); // Disabled verbose, enable with 1L if needed
        curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0L);
        curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 0L);

        res = curl_easy_perform(curl);
        if (res == CURLE_OK) {
            curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &http_code);
        }

        curl_slist_free_all(headers);
        curl_easy_cleanup(curl);
        curl = nullptr; // Reset for next iteration or exit

        if (res != CURLE_OK) {
            EL_ERROR("激活请求 (curl_easy_perform) 失败: {}", curl_easy_strerror(res));
            // Don't cleanup globally here, might be a transient network issue, retry will re-init
            // Only cleanup globally if we are exiting the function due to this error.
            if (polling_attempts + 1 >= kMaxActivationPollAttempts) {
                curl_global_cleanup();
            }
            return ActivationStatus::ERROR; // Or consider retrying for some cURL errors?
        }

        polling_attempts++;
        EL_INFO("激活尝试 {}/{}, HTTP Code: {}, Body: {}", polling_attempts, kMaxActivationPollAttempts, http_code,
                 readBuffer.c_str());

        if (http_code == 200) {
            EL_INFO("设备激活成功.");
            curl_global_cleanup();
            return ActivationStatus::SUCCESS;
        } else if (http_code == 202) {
            if (polling_attempts >= kMaxActivationPollAttempts) {
                EL_WARN("激活请求仍在处理中，但已达到最大轮询次数。");
                break; // Exit loop, will return FAILURE
            }
            EL_INFO("设备激活请求已接受，正在处理中 (pending)... 将在 {}ms 后重试。", kActivationPollDelayMs);
            std::this_thread::sleep_for(std::chrono::milliseconds(kActivationPollDelayMs));
            // Continue to next poll attempt
        } else {
            EL_ERROR("设备激活失败. HTTP Code: {}. Body: {}", http_code, readBuffer.c_str());
            curl_global_cleanup();
            return ActivationStatus::FAILURE;
        }
    } // End of while loop

    EL_WARN("激活轮询结束，未能成功激活。");
    curl_global_cleanup();
    return ActivationStatus::FAILURE; // If loop finishes without success
}

} // namespace AITalk
} // namespace El
